/****************************************************************************
 *
 *   Copyright (c) 2018 PX4 Development Team. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name PX4 nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

#include "FlightTaskManualPositionSmoothVel.hpp"

#include <mathlib/mathlib.h>
#include <float.h>

using namespace matrix;

bool FlightTaskManualPositionSmoothVel::activate()
{
	bool ret = FlightTaskManualPosition::activate();

	reset(Axes::XYZ);

	return ret;
}

void FlightTaskManualPositionSmoothVel::reActivate()
{
	reset(Axes::XY);
}

void FlightTaskManualPositionSmoothVel::reset(Axes axes)
{
	int count;

	switch (axes) {
	case Axes::XY:
		count = 2;
		break;

	case Axes::XYZ:
		count = 3;
		break;

	default:
		count = 0;
		break;
	}

	// TODO: get current accel
	for (int i = 0; i < count; ++i) {
		_smoothing[i].reset(0.f, _velocity(i), _position(i));
	}

	_position_lock_xy_active = false;
	_position_setpoint_xy_locked(0) = NAN;
	_position_setpoint_xy_locked(1) = NAN;
}

void FlightTaskManualPositionSmoothVel::_updateSetpoints()
{
	/* Get yaw setpont, un-smoothed position setpoints.*/
	FlightTaskManualPosition::_updateSetpoints();

	/* Update constraints */
	_smoothing[0].setMaxAccel(MPC_ACC_HOR_MAX.get());
	_smoothing[1].setMaxAccel(MPC_ACC_HOR_MAX.get());
	_smoothing[0].setMaxVel(_constraints.speed_xy);
	_smoothing[1].setMaxVel(_constraints.speed_xy);

	if (_velocity_setpoint(2) < 0.f) { // up
		_smoothing[2].setMaxAccel(MPC_ACC_UP_MAX.get());
		_smoothing[2].setMaxVel(_constraints.speed_up);

	} else { // down
		_smoothing[2].setMaxAccel(MPC_ACC_DOWN_MAX.get());
		_smoothing[2].setMaxVel(_constraints.speed_down);
	}

	Vector2f vel_xy_sp = Vector2f(&_velocity_setpoint(0));
	float jerk[3] = {_jerk_max.get(), _jerk_max.get(), _jerk_max.get()};
	float jerk_xy = _jerk_max.get();

	if (_jerk_min.get() > _jerk_max.get()) {
		_jerk_min.set(0.f);
	}

	if (_jerk_min.get() > FLT_EPSILON) {
		if (vel_xy_sp.length() < FLT_EPSILON) { // Brake
			jerk_xy = _jerk_max.get();

		} else {
			jerk_xy = _jerk_min.get();
		}
	}

	jerk[0] = jerk_xy;
	jerk[1] = jerk_xy;

	/* Check for position unlock
	 * During a position lock -> position unlock transition, we have to make sure that the velocity setpoint
	 * is continuous. We know that the output of the position loop (part of the velocity setpoint) will suddenly become null
	 * and only the feedforward (generated by this flight task) will remain. This is why the previous input of the velocity controller
	 * is used to set current velocity of the trajectory.
	 */
	Vector2f sticks_expo_xy = Vector2f(&_sticks_expo(0));

	if (sticks_expo_xy.length() > FLT_EPSILON) {
		if (_position_lock_xy_active) {
			_smoothing[0].setCurrentVelocity(_velocity_setpoint_feedback(
					0)); // Start the trajectory at the current velocity setpoint
			_smoothing[1].setCurrentVelocity(_velocity_setpoint_feedback(1));
			_position_setpoint_xy_locked(0) = NAN;
			_position_setpoint_xy_locked(1) = NAN;
		}

		_position_lock_xy_active = false;
	}

	for (int i = 0; i < 3; ++i) {
		_smoothing[i].setMaxJerk(jerk[i]);
		_smoothing[i].updateDurations(_deltatime, _velocity_setpoint(i));
	}

	VelocitySmoothing::timeSynchronization(_smoothing, 2); // Synchronize x and y only

	if (_position_lock_xy_active) {
		// Check if a reset event has happened.
		if (_sub_vehicle_local_position->get().xy_reset_counter != _reset_counter) {
			// Reset the XY axes
			_smoothing[0].setCurrentPosition(_position(0));
			_smoothing[1].setCurrentPosition(_position(1));
			_reset_counter = _sub_vehicle_local_position->get().xy_reset_counter;
		}
	}

	Vector3f pos_sp_smooth;
	Vector3f accel_sp_smooth;

	for (int i = 0; i < 3; ++i) {
		if (!_position_lock_xy_active) {
			_smoothing[i].setCurrentPosition(_position(i));
		}

		_smoothing[i].integrate(accel_sp_smooth(i), _vel_sp_smooth(i), pos_sp_smooth(i));
		_velocity_setpoint(i) = _vel_sp_smooth(i); // Feedforward
	}

	// Check for position lock transition
	if (Vector2f(_vel_sp_smooth).length() < 0.01f &&
	    Vector2f(accel_sp_smooth).length() < .2f &&
	    sticks_expo_xy.length() <= FLT_EPSILON) {
		_position_setpoint_xy_locked(0) = pos_sp_smooth(0);
		_position_setpoint_xy_locked(1) = pos_sp_smooth(1);
		_position_lock_xy_active = true;
	}

	_position_setpoint(0) = _position_setpoint_xy_locked(0);
	_position_setpoint(1) = _position_setpoint_xy_locked(1);
}
